package com.example.oom;

/**
 * 模拟 java.lang.OutOfMemoryError: Metaspace
 * 原理：不断创建新的 ClassLoader，每个 ClassLoader 定义一个新类，
 * 且持有引用不释放，最终占满元空间。
 *
 * 运行建议：
 *   javac MetaspaceOOM.java
 *   java -XX:MaxMetaspaceSize=64m MetaspaceOOM
 *
 * 观察输出：
 *   java.lang.OutOfMemoryError: Metaspace
 */
public class MetaspaceOOM {

    static class OOMClassLoader extends ClassLoader {
        public Class<?> createClass(String name) {
            byte[] code = generateSimpleClass(name.replace('.', '/'));
            return defineClass(name, code, 0, code.length);
        }
    }

    public static void main(String[] args) {
        System.out.println("开始模拟 Metaspace OOM...");
        System.out.println("建议使用参数：-XX:MaxMetaspaceSize=64m");
        try {
            // 用一个集合强引用所有的 ClassLoader，防止被GC
            java.util.List<ClassLoader> loaders = new java.util.ArrayList<>();
            int i = 0;
            while (true) {
                String className = "demo.Generated" + i++;
                OOMClassLoader loader = new OOMClassLoader();
                loader.createClass(className);
                loaders.add(loader); // 关键：持有引用不释放
                if (i % 1000 == 0) {
                    System.out.println("已加载类数量: " + i);
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成一个最简单的 class 文件字节码：
     * public class XXX { public XXX() {} }
     */
    private static byte[] generateSimpleClass(String internalName) {
        try (java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
             java.io.DataOutputStream dout = new java.io.DataOutputStream(out)) {

            dout.writeInt(0xCAFEBABE);     // magic
            dout.writeShort(0);            // minor
            dout.writeShort(52);           // major (Java 8)
            dout.writeShort(10);           // constant_pool_count

            // 常量池
            writeUtf8(dout, internalName);         // #1
            writeUtf8(dout, "java/lang/Object");   // #2
            dout.writeByte(7); dout.writeShort(1); // #3: this_class
            dout.writeByte(7); dout.writeShort(2); // #4: super_class
            writeUtf8(dout, "<init>");             // #5
            writeUtf8(dout, "()V");                // #6
            dout.writeByte(12); dout.writeShort(5); dout.writeShort(6); // #7: NameAndType
            dout.writeByte(10); dout.writeShort(4); dout.writeShort(7); // #8: Methodref Object.<init>
            writeUtf8(dout, "Code");               // #9

            // 类头
            dout.writeShort(0x0021); // ACC_PUBLIC | ACC_SUPER
            dout.writeShort(3);      // this_class
            dout.writeShort(4);      // super_class
            dout.writeShort(0);      // interfaces_count
            dout.writeShort(0);      // fields_count
            dout.writeShort(1);      // methods_count

            // method: <init>
            dout.writeShort(0x0001); // public
            dout.writeShort(5);      // name_index
            dout.writeShort(6);      // descriptor_index
            dout.writeShort(1);      // attributes_count

            dout.writeShort(9); // "Code"
            byte[] code = {
                    0x2A,          // aload_0
                    (byte) 0xB7, 0x00, 0x08, // invokespecial #8
                    (byte) 0xB1   // return
            };
            int len = 2 + 2 + 4 + code.length + 2 + 2;
            dout.writeInt(len);
            dout.writeShort(1); // max_stack
            dout.writeShort(1); // max_locals
            dout.writeInt(code.length);
            dout.write(code);
            dout.writeShort(0); // exception_table_length
            dout.writeShort(0); // attributes_count
            dout.writeShort(0); // class attributes_count

            dout.flush();
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void writeUtf8(java.io.DataOutputStream dout, String s) throws java.io.IOException {
        dout.writeByte(1);
        byte[] bytes = s.getBytes("UTF-8");
        dout.writeShort(bytes.length);
        dout.write(bytes);
    }
}
